package com.amkai.aes.webservice.binary.objecthandler.prefcards;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.Query;

import com.amkai.aes.aescommon.schedule.AppointmentHelper;
import com.amkai.aes.aescommon.service.binary.job.prefcards.PrefCardPrinterPatientQueryJob;
import com.amkai.aes.aescommon.service.binary.job.prefcards.PrefCardPrinterPatientQueryParams;
import com.amkai.aes.aescommon.service.binary.job.prefcards.PrefCardPrinterPatientQueryResult;
import com.amkai.aes.aescommon.service.binary.job.prefcards.PreferenceCardPrintingData;
import com.amkai.aes.generatedbeans.physstaff.Physician;
import com.amkai.aes.generatedbeans.pmcase.PmCase;
import com.amkai.aes.generatedbeans.pmcase.PmCaseStatusKind;
import com.amkai.aes.generatedbeans.preferencecard.PreferenceCard;
import com.amkai.aes.generatedbeans.preferencecard.ReleatedProcedure;
import com.amkai.aes.generatedbeans.preferencecard.SurgeonPreferences;
import com.amkai.aes.generatedbeans.reference.PhysStaffReference;
import com.amkai.aes.generatedbeans.scheduling.AbstractAppointment;
import com.amkai.aes.generatedbeans.scheduling.AppointmentPreferenceCardAssociation;
import com.amkai.aes.generatedbeans.scheduling.ResourceConnectionRole;
import com.amkai.aes.generatedbeans.scheduling.ResourceKind;
import com.amkai.aes.generatedbeans.scheduling.SchedulingResource;
import com.amkai.aes.generatedbeans.scheduling.SurgeonConnectionProperty;
import com.amkai.aes.generatedbeans.scheduling.SurgeryProcedureUsage;
import com.amkai.aes.server.util.AesServerUtils;
import com.amkai.aes.webservice.binary.JobObjectHandler;
import com.amkai.common.extensions.hibernate.config.SiteManager;
import com.amkai.common.extensions.hibernate.config.manager.SessionManager;
import com.amkai.scheduler.base.manager.ResourceConnectionManager;

public class PrefCardPrintingPatientQueryHandler implements JobObjectHandler<PrefCardPrinterPatientQueryJob, PrefCardPrinterPatientQueryParams, PrefCardPrinterPatientQueryResult> {
	private static final Log log = LogFactory.getLog(PrefCardPrintingPatientQueryHandler.class);
	
	private final String appointmentQuery = 
		"SELECT app FROM AbstractAppointment app "+
		"WHERE app.pmCase = :pmcase ORDER by id";

	private final String pmCaseQuery = 
		"SELECT pmC FROM PmCase pmC " +
			"LEFT JOIN pmC.organizationSpecificProperties as osp "+
			"LEFT JOIN FETCH pmC.abstractAppointment aa "+
			"LEFT JOIN aa.resourceConnections rr "+
		"WHERE " +
			"osp.baseOrganization.id = :beId AND "+
			"aa.department.id = :beId AND " + 
			"aa.appointmentPreferenceCardAssociations is not empty AND "+
			"rr.roleWrapper.name = :targetRoleName AND "+
			"rr.reservation.schedulingResource.kindWrapper.name = :roomKindName AND "+
			"YEAR(pmC.dateOfService) = :pmdateofserviceyear AND MONTH(pmC.dateOfService) = :pmdateofservicemonth AND DAY(pmC.dateOfService) = :pmdateofserviceday AND " +
			"aa.statusWrapper.id not in (" + PmCaseStatusKind.CANCELED.ordinal() +") AND pmC.statusWrapper.id not in (" + PmCaseStatusKind.CANCELED.ordinal() +") " +
		"ORDER BY "+
			"rr.reservation.schedulingResource.title";

	@SuppressWarnings("unchecked")
	@Override
	public PrefCardPrinterPatientQueryResult process(PrefCardPrinterPatientQueryJob job, PrefCardPrinterPatientQueryParams param) throws Exception {
		PrefCardPrinterPatientQueryResult result = null;
		SessionManager sm = SiteManager.Start();
		try {
			sm.openSession();

			if (param.getDOSDate() == null) {

				Map<PmCase, List<PreferenceCardPrintingData>> resultMap = new HashMap<PmCase, List<PreferenceCardPrintingData>>();

				if (param.getPmCases().size() > 0) {
					result = new PrefCardPrinterPatientQueryResult();

					groupByCase(resultMap, sm, param.getPmCases(), true);
					result.setResultData(resultMap);
				}
				return result;
			} else {
				Map<SchedulingResource, LinkedHashMap<PmCase, List<PreferenceCardPrintingData>>> rooms = new HashMap<SchedulingResource, LinkedHashMap<PmCase, List<PreferenceCardPrintingData>>>();

				// AES-15373
				Calendar dosCalendar = Calendar.getInstance(job.getTimeZone());
				dosCalendar.setTime(param.getDOSDate());
				Query caseQuery = sm.execute(pmCaseQuery);
				// AES-13209 - BE specific query
				caseQuery = caseQuery.	setParameter("targetRoleName", ResourceConnectionRole.TARGET.name()).
										setParameter("roomKindName", ResourceKind.ROOM.name()).
										setParameter("pmdateofserviceyear", dosCalendar.get(Calendar.YEAR)).
										setParameter("pmdateofservicemonth", dosCalendar.get(Calendar.MONTH) + 1).
										setParameter("pmdateofserviceday", dosCalendar.get(Calendar.DAY_OF_MONTH)).
										setParameter("beId", job.getModuleDepartment().getId());
				List<PmCase> caseResults = caseQuery.list();

				SchedulingResource lastRoom = null;
				List<PmCase> lastRoomCases = new ArrayList<PmCase>();

				for (PmCase pmCase : caseResults) {
					AbstractAppointment aa = pmCase.getAbstractAppointment();
					SchedulingResource currentRoom = ResourceConnectionManager.getSchedulingResource(aa, ResourceConnectionRole.TARGET);
					if (lastRoom != null && !currentRoom.equals(lastRoom)) {
						LinkedHashMap<PmCase, List<PreferenceCardPrintingData>> lastRoomMap = new LinkedHashMap<PmCase, List<PreferenceCardPrintingData>>();
						rooms.put(lastRoom, lastRoomMap);
						groupByCase(lastRoomMap, sm, lastRoomCases, false);

						lastRoomCases.clear();
					}
					lastRoomCases.add(pmCase);

					lastRoom = currentRoom;
				}
				if (lastRoom != null) {
					LinkedHashMap<PmCase, List<PreferenceCardPrintingData>> lastRoomMap = new LinkedHashMap<PmCase, List<PreferenceCardPrintingData>>();
					rooms.put(lastRoom, lastRoomMap);
					groupByCase(lastRoomMap, sm, lastRoomCases, false);
				}

				if (rooms.size() > 0) {
					result = new PrefCardPrinterPatientQueryResult();
					result.setRooms(rooms);
					log.debug("rooms.size : " + rooms.size());
				}

			}
		} catch (Exception ex) {
			ex.printStackTrace();
		} finally {
			sm.closeSession();
		}
		return result;

	}

	private void groupByCase(Map<PmCase, List<PreferenceCardPrintingData>> resultMap, SessionManager sm, Collection<PmCase> pmCases, boolean insertSurgeonsWithoutPrefCards) throws Exception {
		for (PmCase currentCase : pmCases) {
			// the appointment for the case
			AbstractAppointment appL = currentCase.getAbstractAppointment();
			if (appL == null) {
				Query appQ = sm.execute(appointmentQuery).setParameter("pmcase", currentCase);
				appQ.setMaxResults(1);
				appL = (AbstractAppointment) appQ.uniqueResult();
			} else {
				appL = AesServerUtils.getOnlineBean(sm, appL);
			}
			if (appL == null) {
				log.error("WARN Appointment not found for case : " + currentCase);
				continue;
			}

			for (AppointmentPreferenceCardAssociation assoc : appL.getAppointmentPreferenceCardAssociations()) {
				assoc.getPreferenceCard().getId();
				assoc.getPreferenceCard().getCardName();
			}

			List<SurgeryProcedureUsage> surgeryProcedureUsages = AppointmentHelper.extractAppointmentSurgeryProcedureUsages(appL);
			HashMap<PreferenceCard, PreferenceCardPrintingData> dataMap = new HashMap<PreferenceCard, PreferenceCardPrintingData>();
			List<PreferenceCardPrintingData> dataList = new ArrayList<PreferenceCardPrintingData>();
			if (surgeryProcedureUsages != null) {
				int i = 0;
				for (SurgeryProcedureUsage usage : surgeryProcedureUsages) {
					usage.getProcedure().getName();
					for (SurgeonConnectionProperty prop : usage.getSurgeonConnectionProperty()) {
						if (prop.getResourceConnection().getRole() == ResourceConnectionRole.SURGEON) {
							createSurgeonProcedureItem(dataMap, dataList, sm, currentCase, appL, usage, prop, insertSurgeonsWithoutPrefCards, true, i++);
						}
					}
				}
				i = 0;
				for (SurgeryProcedureUsage usage : surgeryProcedureUsages) {
					for (SurgeonConnectionProperty prop : usage.getSurgeonConnectionProperty()) {
						if (prop.getResourceConnection().getRole() == ResourceConnectionRole.SURGEON) {
							createSurgeonProcedureItem(dataMap, dataList, sm, currentCase, appL, usage, prop, insertSurgeonsWithoutPrefCards, false, i++);
						}
					}
				}
				// AES-12542 - add scheduled pc w/o scheduled procedure
				while (i==dataList.size())
					createSurgeonProcedureItem(dataMap, dataList, sm, currentCase, appL, null, null, insertSurgeonsWithoutPrefCards, false, i++);
			}
			if (insertSurgeonsWithoutPrefCards) {
				resultMap.put(currentCase, dataList);
			} else {
				while (dataList.remove(null))
					;
				if (dataList.size() > 0) {
					resultMap.put(currentCase, dataList);
				}
			}
		}
	}

	private void createSurgeonProcedureItem(HashMap<PreferenceCard, PreferenceCardPrintingData> dataMap, List<PreferenceCardPrintingData> dataList, SessionManager sm, PmCase currentCase, AbstractAppointment appL, SurgeryProcedureUsage usage, SurgeonConnectionProperty prop, boolean insertSurgeonsWithoutPrefCards, boolean processMainProcedure, int procedureIndex) throws Exception {
		Physician phys = null;
		if (prop!=null) {
			Long physRefId = prop.getResourceConnection().getReservation().getSchedulingResource().getReference().getId();
			PhysStaffReference physRef = sm.get(PhysStaffReference.class, physRefId);
			phys = sm.get(Physician.class, physRef.getPhysStaff().getId());
		}
		if (prop==null || phys!=null) {

			PreferenceCard preferenceCard = null;
			PreferenceCardPrintingData data = null;
			if (processMainProcedure) {
				for (AppointmentPreferenceCardAssociation assoc : appL.getAppointmentPreferenceCardAssociations()) {
					PreferenceCard pc = assoc.getPreferenceCard();

					// KKORNEL: AES-8241
					if ((!pc.isDeleted()) && (!dataMap.containsKey(pc))) {
						if (pc.getMainCode().equals(usage.getProcedure())) {
							preferenceCard = pc;
							break;
						}
					}
				}
				if (preferenceCard != null) {
					data = new PreferenceCardPrintingData();
					data.setPreferenceCardId(preferenceCard.getId());
					data.setPreferenceCardName(preferenceCard.getCardName());
					// AES-12542 - this is a main pc if this is the first procedure
					data.setMain(procedureIndex==0);
					dataMap.put(preferenceCard, data);
					dataList.add(data);
				} else {
					dataList.add(null);
					return;
				}
			} else {
				for (AppointmentPreferenceCardAssociation assoc : appL.getAppointmentPreferenceCardAssociations()) {
					PreferenceCard pc = assoc.getPreferenceCard();
					// KKORNEL: AES-8241
					if ((!pc.isDeleted()) && (!dataMap.containsKey(pc))) {
						// AES-12542
						if (usage==null) {
							preferenceCard = pc;
						} else {
							for (ReleatedProcedure rp : pc.getReleatedProcedures()) {
								if (rp.getProcedure().equals(usage.getProcedure())) {
									preferenceCard = pc;
									break;
								}
							}
						}
					}
					if (preferenceCard!=null) {
						break;
					}
				}
				if (preferenceCard != null) {
					data = new PreferenceCardPrintingData();
					data.setPreferenceCardId(preferenceCard.getId());
					data.setPreferenceCardName(preferenceCard.getCardName());
					// AES-12542 - we count this as main pc if this is the first procedure and the procedure equals the main code
					data.setMain(procedureIndex==0 && usage!=null && preferenceCard.getMainCode().equals(usage.getProcedure()));
					dataMap.put(preferenceCard, data);
					if (procedureIndex==dataList.size())
						dataList.add(data);
					else
						dataList.set(procedureIndex, data);
				} else if (insertSurgeonsWithoutPrefCards && procedureIndex<dataList.size() && dataList.get(procedureIndex)==null) {
					data = new PreferenceCardPrintingData();
					data.setMain(false);
					dataList.set(procedureIndex, data);
				} else {
					return;
				}
			}

			if (phys!=null) {
				phys.getName();
				phys.getId();
				for (SurgeonPreferences surgPref : phys.getSurgeonPreferences()) {
					surgPref.getId();
				}
			}

			data.setSurgeryCenterAppointmentId(appL.getId());
			data.setProcedure(usage==null ? null : usage.getProcedure());
			data.setSurgeon(phys);
			data.setPmCaseId(currentCase.getId());
			data.setPatientId(currentCase.getPatient().getId());
		}

	}
}
